use std::fmt::Display;
use poem_openapi::{payload::Json, ApiResponse, Object};
use poem_openapi::types::{ParseFromJSON, ToJSON};
use poem::{http::StatusCode, Error};


#[derive(ApiResponse)]
#[oai(bad_request_handler = "bad_request_handler")]
pub enum Responder<T>
where
    T: ParseFromJSON + ToJSON,
{
    #[oai(status = 200)]
    Ok(Json<ResponseObject<T>>),
    #[oai(status = 400)]
    Err(Json<ResponseObject<T>>),
    #[oai(status = 401)]
    Unauthorized(Json<ResponseObject<T>>),
    #[oai(status = 403)]
    Forbidden(Json<ResponseObject<T>>),
    #[oai(status = 404)]
    NotFound(Json<ResponseObject<T>>),
    #[oai(status = 406)]
    NotAcceptable(Json<ResponseObject<T>>),
    #[oai(status = 409)]
    Conflict(Json<ResponseObject<T>>),
    #[oai(status = 410)]
    Gone(Json<ResponseObject<T>>),
    #[oai(status = 412)]
    PreconditionFailed(Json<ResponseObject<T>>),
    #[oai(status = 417)]
    ExpectationFailed(Json<ResponseObject<T>>),
    #[oai(status = 500)]
    InternalServerError(Json<ResponseObject<T>>),
}

#[derive(Debug, Object)]
pub struct ResponseObject<T>
where
    T: ParseFromJSON + ToJSON,
{
    pub code:  Option<u16>,
    #[oai(skip_serializing_if = "Option::is_none")]
    pub msg: Option<String>,
    #[oai(skip_serializing_if = "Option::is_none")]
    pub data: Option<T>,
}

fn bad_request_handler<T>(err: Error) -> Responder<T>
where
    T: ParseFromJSON + ToJSON,
{
    let err_msg = err.to_string();
    let mut obj = ResponseObject {
        code: Some(StatusCode::BAD_REQUEST.as_u16()),
        msg: Some(err_msg.to_string()),
        data: None,
    };
    if err_msg.eq(StatusCode::UNAUTHORIZED.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::UNAUTHORIZED.as_u16());
        return Responder::Unauthorized(Json(obj));
    } else if err_msg.eq("Unauthorized request") || err_msg.eq(StatusCode::FORBIDDEN.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::FORBIDDEN.as_u16());
        obj.msg = Some(StatusCode::FORBIDDEN.canonical_reason().unwrap().to_string());
        return Responder::Forbidden(Json(obj));
    } else if err_msg.eq(StatusCode::NOT_FOUND.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::NOT_FOUND.as_u16());
        return Responder::NotFound(Json(obj));
    } else if err_msg.eq(StatusCode::NOT_ACCEPTABLE.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::NOT_ACCEPTABLE.as_u16());
        return Responder::NotAcceptable(Json(obj));
    } else if err_msg.eq(StatusCode::CONFLICT.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::CONFLICT.as_u16());
        return Responder::Conflict(Json(obj));
    } else if err_msg.eq(StatusCode::GONE.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::GONE.as_u16());
        return Responder::Gone(Json(obj));
    } else if err_msg.eq(StatusCode::PRECONDITION_FAILED.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::PRECONDITION_FAILED.as_u16());
        return Responder::PreconditionFailed(Json(obj));
    } else if err_msg.eq(StatusCode::EXPECTATION_FAILED.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::EXPECTATION_FAILED.as_u16());
        return Responder::ExpectationFailed(Json(obj));
    } else if err_msg.eq(StatusCode::INTERNAL_SERVER_ERROR.canonical_reason().unwrap()) {
        obj.code = Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16());
        return Responder::InternalServerError(Json(obj));
    }
    Responder::Err(Json(obj))
}

impl<T> Responder<T>
where
    T: ParseFromJSON + ToJSON,
{
    pub fn data(data: T) -> Self {
        let obj = ResponseObject {
            code: Some(StatusCode::OK.as_u16()),
            msg: None,
            data: Some(data),
        };
        Responder::Ok(Json(obj))
    }

    pub fn err(err: impl Display) -> Self {
        let mut obj = ResponseObject {
            code: Some(StatusCode::BAD_REQUEST.as_u16()),
            msg: Some(err.to_string()),
            data: None,
        };
        if err.to_string().eq(StatusCode::UNAUTHORIZED.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::UNAUTHORIZED.as_u16());
            return Responder::Unauthorized(Json(obj));
        } else if err.to_string().eq(StatusCode::FORBIDDEN.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::FORBIDDEN.as_u16());
            return Responder::Forbidden(Json(obj));
        } else if err.to_string().eq(StatusCode::NOT_FOUND.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::NOT_FOUND.as_u16());
            return Responder::NotFound(Json(obj));
        } else if err.to_string().eq(StatusCode::NOT_ACCEPTABLE.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::NOT_ACCEPTABLE.as_u16());
            return Responder::NotAcceptable(Json(obj));
        } else if err.to_string().eq(StatusCode::CONFLICT.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::CONFLICT.as_u16());
            return Responder::Conflict(Json(obj));
        } else if err.to_string().eq(StatusCode::GONE.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::GONE.as_u16());
            return Responder::Gone(Json(obj));
        } else if err.to_string().eq(StatusCode::PRECONDITION_FAILED.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::PRECONDITION_FAILED.as_u16());
            return Responder::PreconditionFailed(Json(obj));
        } else if err.to_string().eq(StatusCode::EXPECTATION_FAILED.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::EXPECTATION_FAILED.as_u16());
            return Responder::ExpectationFailed(Json(obj));
        } else if err.to_string().eq(StatusCode::INTERNAL_SERVER_ERROR.canonical_reason().unwrap()) {
            obj.code = Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16());
            return Responder::InternalServerError(Json(obj));
        }
        Responder::Err(Json(obj))
    }

    pub fn ok() -> Self {
        let obj = ResponseObject {
            code: Some(StatusCode::OK.as_u16()),
            msg: None,
            data: None,
        };
        Responder::Ok(Json(obj))
    }
}
